﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Security;
using System.Security.AccessControl;
using System.Security.Principal;
using System.IO;
namespace ModifyDirectoryCanonicalizeDacl
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 2)
            {
                DirectoryInfo dirInfo = new DirectoryInfo(args[1].ToString());
                switch (args[0].ToString().ToUpper())
                {
                    case "RUN":
                        checkFiles(dirInfo, true);
                        break;
                    case "TEST":
                        checkFiles(dirInfo, false);
                        break;
                    default:
                        Console.WriteLine(@"Test \\PATH\To\Directory\To\Check");
                        Console.WriteLine(@"Run \\PATH\To\Directory\To\Check");
                        break;
                }
            }
            else
            {
                Console.WriteLine(@"Test \\PATH\To\Directory\To\Check");
                Console.WriteLine(@"Run \\PATH\To\Directory\To\Check");
            }
        }
        internal static void checkFiles(DirectoryInfo dir, bool run)
        {
            foreach (FileInfo fileInfo in dir.GetFiles())
            {
                FileSecurity fileSecurity = fileInfo.GetAccessControl();
                CanonicalizeDacl(fileInfo, fileSecurity, run);
            }
            foreach (DirectoryInfo directory in dir.GetDirectories())
            {
                CanonicalizeDacl(directory,directory.GetAccessControl(),run);
                checkFiles(directory, run);
            }
        }
        internal static void CanonicalizeDacl(FileInfo fileInfo, NativeObjectSecurity objectSecurity, bool run)
        {
            if (objectSecurity == null)
            {
                throw new ArgumentNullException("objectSecurity");
            }

            if (objectSecurity.AreAccessRulesCanonical)
            {
                return;
            }
            else
            {
                if (run)
                {
                    Console.WriteLine("Found File with bad DACLS " + fileInfo.Name);
                    // A canonical ACL must have ACES sorted according to the following order:
                    //   1. Access-denied on the object
                    //   2. Access-denied on a child or property
                    //   3. Access-allowed on the object
                    //   4. Access-allowed on a child or property
                    //   5. All inherited ACEs 
                    RawSecurityDescriptor descriptor = new RawSecurityDescriptor(objectSecurity.GetSecurityDescriptorSddlForm(AccessControlSections.Access));

                    List<CommonAce> implicitDenyDacl = new List<CommonAce>();
                    List<CommonAce> implicitDenyObjectDacl = new List<CommonAce>();
                    List<CommonAce> inheritedDacl = new List<CommonAce>();
                    List<CommonAce> implicitAllowDacl = new List<CommonAce>();
                    List<CommonAce> implicitAllowObjectDacl = new List<CommonAce>();

                    foreach (CommonAce ace in descriptor.DiscretionaryAcl)
                    {
                        if ((ace.AceFlags & AceFlags.Inherited) == AceFlags.Inherited)
                        {
                            inheritedDacl.Add(ace);
                        }
                        else
                        {
                            switch (ace.AceType)
                            {
                                case AceType.AccessAllowed:
                                    implicitAllowDacl.Add(ace);
                                    break;

                                case AceType.AccessDenied:
                                    implicitDenyDacl.Add(ace);
                                    break;

                                case AceType.AccessAllowedObject:
                                    implicitAllowObjectDacl.Add(ace);
                                    break;

                                case AceType.AccessDeniedObject:
                                    implicitDenyObjectDacl.Add(ace);
                                    break;
                            }
                        }
                    }

                    Int32 aceIndex = 0;
                    RawAcl newDacl = new RawAcl(descriptor.DiscretionaryAcl.Revision, descriptor.DiscretionaryAcl.Count);
                    implicitDenyDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
                    implicitDenyObjectDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
                    implicitAllowDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
                    implicitAllowObjectDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
                    inheritedDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));

                    if (aceIndex != descriptor.DiscretionaryAcl.Count)
                    {
                        System.Diagnostics.Debug.Fail("The DACL cannot be canonicalized since it would potentially result in a loss of information");
                        return;
                    }

                    descriptor.DiscretionaryAcl = newDacl;
                    objectSecurity.SetSecurityDescriptorSddlForm(descriptor.GetSddlForm(AccessControlSections.Access));

                    fileInfo.SetAccessControl((FileSecurity)objectSecurity);
                }
                else
                {
                    Console.WriteLine("Found File with bad DACLS " + fileInfo.Name);
                }
            }
        }
        internal static void CanonicalizeDacl(DirectoryInfo DirInfo, NativeObjectSecurity objectSecurity, bool run)
        {
            if (objectSecurity == null)
            {
                throw new ArgumentNullException("objectSecurity");
            }

            if (objectSecurity.AreAccessRulesCanonical)
            {
                return;
            }
            else
            {
                if (run)
                {
                    Console.WriteLine("Found Directory with bad DACLS " + DirInfo.FullName);
                    // A canonical ACL must have ACES sorted according to the following order:
                    //   1. Access-denied on the object
                    //   2. Access-denied on a child or property
                    //   3. Access-allowed on the object
                    //   4. Access-allowed on a child or property
                    //   5. All inherited ACEs 
                    RawSecurityDescriptor descriptor = new RawSecurityDescriptor(objectSecurity.GetSecurityDescriptorSddlForm(AccessControlSections.Access));

                    List<CommonAce> implicitDenyDacl = new List<CommonAce>();
                    List<CommonAce> implicitDenyObjectDacl = new List<CommonAce>();
                    List<CommonAce> inheritedDacl = new List<CommonAce>();
                    List<CommonAce> implicitAllowDacl = new List<CommonAce>();
                    List<CommonAce> implicitAllowObjectDacl = new List<CommonAce>();

                    foreach (CommonAce ace in descriptor.DiscretionaryAcl)
                    {
                        if ((ace.AceFlags & AceFlags.Inherited) == AceFlags.Inherited)
                        {
                            inheritedDacl.Add(ace);
                        }
                        else
                        {
                            switch (ace.AceType)
                            {
                                case AceType.AccessAllowed:
                                    implicitAllowDacl.Add(ace);
                                    break;

                                case AceType.AccessDenied:
                                    implicitDenyDacl.Add(ace);
                                    break;

                                case AceType.AccessAllowedObject:
                                    implicitAllowObjectDacl.Add(ace);
                                    break;

                                case AceType.AccessDeniedObject:
                                    implicitDenyObjectDacl.Add(ace);
                                    break;
                            }
                        }
                    }

                    Int32 aceIndex = 0;
                    RawAcl newDacl = new RawAcl(descriptor.DiscretionaryAcl.Revision, descriptor.DiscretionaryAcl.Count);
                    implicitDenyDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
                    implicitDenyObjectDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
                    implicitAllowDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
                    implicitAllowObjectDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
                    inheritedDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));

                    if (aceIndex != descriptor.DiscretionaryAcl.Count)
                    {
                        System.Diagnostics.Debug.Fail("The DACL cannot be canonicalized since it would potentially result in a loss of information");
                        return;
                    }

                    descriptor.DiscretionaryAcl = newDacl;
                    objectSecurity.SetSecurityDescriptorSddlForm(descriptor.GetSddlForm(AccessControlSections.Access));

                    DirInfo.SetAccessControl((DirectorySecurity)objectSecurity);
                }
                else
                {
                    Console.WriteLine("Found Directory with bad DACLS " + DirInfo.FullName);
                }
            }
        }
    }
}
